'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.UNSELECTABLE_ATTRIBUTE = exports.UNSELECTABLE_STYLE = undefined;
exports.toTitle = toTitle;
exports.toArray = toArray;
exports.createRef = createRef;
exports.flatToHierarchy = flatToHierarchy;
exports.resetAriaId = resetAriaId;
exports.generateAriaId = generateAriaId;
exports.isLabelInValue = isLabelInValue;
exports.parseSimpleTreeData = parseSimpleTreeData;
exports.convertTreeToData = convertTreeToData;
exports.convertDataToEntities = convertDataToEntities;
exports.isPosRelated = isPosRelated;
exports.cleanEntity = cleanEntity;
exports.getFilterTree = getFilterTree;
exports.formatInternalValue = formatInternalValue;
exports.getLabel = getLabel;
exports.formatSelectorValue = formatSelectorValue;
exports.calcUncheckConduct = calcUncheckConduct;

var _typeof2 = require('babel-runtime/helpers/typeof');

var _typeof3 = _interopRequireDefault(_typeof2);

var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');

var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);

var _extends2 = require('babel-runtime/helpers/extends');

var _extends3 = _interopRequireDefault(_extends2);

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _warning = require('warning');

var _warning2 = _interopRequireDefault(_warning);

var _SelectNode = require('./SelectNode');

var _SelectNode2 = _interopRequireDefault(_SelectNode);

var _strategies = require('./strategies');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

// When treeNode not provide key, and we will use value as key.
// Some time value is empty, we should pass it instead.
var KEY_OF_VALUE_EMPTY = 'RC_TREE_SELECT_KEY_OF_VALUE_EMPTY';

var warnDeprecatedLabel = false;

// =================== MISC ====================
function toTitle(title) {
  if (typeof title === 'string') {
    return title;
  }
  return null;
}

function toArray(data) {
  if (!data) return [];

  return Array.isArray(data) ? data : [data];
}

// Shallow copy of React 16.3 createRef api
function createRef() {
  var func = function setRef(node) {
    func.current = node;
  };
  return func;
}

// =============== Legacy ===============
var UNSELECTABLE_STYLE = exports.UNSELECTABLE_STYLE = {
  userSelect: 'none',
  WebkitUserSelect: 'none'
};

var UNSELECTABLE_ATTRIBUTE = exports.UNSELECTABLE_ATTRIBUTE = {
  unselectable: 'unselectable'
};

/**
 * Convert position list to hierarchy structure.
 * This is little hack since use '-' to split the position.
 */
function flatToHierarchy(positionList) {
  if (!positionList.length) {
    return [];
  }

  var entrances = {};

  // Prepare the position map
  var posMap = {};
  var parsedList = positionList.slice().map(function (entity) {
    var clone = (0, _extends3['default'])({}, entity, {
      fields: entity.pos.split('-')
    });
    delete clone.children;
    return clone;
  });

  parsedList.forEach(function (entity) {
    posMap[entity.pos] = entity;
  });

  parsedList.sort(function (a, b) {
    return a.fields.length - b.fields.length;
  });

  // Create the hierarchy
  parsedList.forEach(function (entity) {
    var parentPos = entity.fields.slice(0, -1).join('-');
    var parentEntity = posMap[parentPos];

    if (!parentEntity) {
      entrances[entity.pos] = entity;
    } else {
      parentEntity.children = parentEntity.children || [];
      parentEntity.children.push(entity);
    }

    // Some time position list provide `key`, we don't need it
    delete entity.key;
    delete entity.fields;
  });

  return Object.keys(entrances).map(function (key) {
    return entrances[key];
  });
}

// =============== Accessibility ===============
var ariaId = 0;

function resetAriaId() {
  ariaId = 0;
}

function generateAriaId(prefix) {
  ariaId += 1;
  return prefix + '_' + ariaId;
}

function isLabelInValue(props) {
  var treeCheckable = props.treeCheckable,
      treeCheckStrictly = props.treeCheckStrictly,
      labelInValue = props.labelInValue;

  if (treeCheckable && treeCheckStrictly) {
    return true;
  }
  return labelInValue || false;
}

// =================== Tree ====================
function parseSimpleTreeData(treeData, _ref) {
  var id = _ref.id,
      pId = _ref.pId,
      rootPId = _ref.rootPId;

  var keyNodes = {};
  var rootNodeList = [];

  // Fill in the map
  var nodeList = treeData.map(function (node) {
    var clone = (0, _extends3['default'])({}, node);
    var key = clone[id];
    keyNodes[key] = clone;
    return clone;
  });

  // Connect tree
  nodeList.forEach(function (node) {
    var parentKey = node[pId];
    var parent = keyNodes[parentKey];

    // Fill parent
    if (parent) {
      parent.children = parent.children || [];
      parent.children.push(node);
    }

    // Fill root tree node
    if (parentKey === rootPId || !parent && rootPId === null) {
      rootNodeList.push(node);
    }
  });

  return rootNodeList;
}

/**
 * `Tree` use `key` to track state but it will changed by React.
 * We need convert it back to the data and re-generate by `key`.
 * This is will cause performance issue.
 */
function convertTreeToData(treeNodes) {
  return _react2['default'].Children.map(treeNodes || [], function (node) {
    if (!_react2['default'].isValidElement(node) || !node.type || !node.type.isTreeNode) {
      return null;
    }

    var key = node.key,
        props = node.props;

    return (0, _extends3['default'])({}, props, {
      key: key,
      children: convertTreeToData(props.children)
    });
  }).filter(function (data) {
    return data;
  });
}

/**
 * Convert `treeData` to TreeNode List contains the mapping data.
 */
function convertDataToEntities(treeData) {
  var list = toArray(treeData);

  var valueEntities = {};
  var keyEntities = {};
  var posEntities = {};

  function traverse(subTreeData, parentPos) {
    var subList = toArray(subTreeData);

    return subList.map(function (_ref2, index) {
      var key = _ref2.key,
          title = _ref2.title,
          label = _ref2.label,
          value = _ref2.value,
          children = _ref2.children,
          nodeProps = (0, _objectWithoutProperties3['default'])(_ref2, ['key', 'title', 'label', 'value', 'children']);

      var pos = parentPos + '-' + index;

      var entity = { key: key, value: value, pos: pos };

      // This may cause some side effect, need additional check
      entity.key = entity.key || value;
      if (!entity.key && entity.key !== 0) {
        entity.key = KEY_OF_VALUE_EMPTY;
      }

      // Fill children
      entity.parent = posEntities[parentPos];
      if (entity.parent) {
        entity.parent.children = entity.parent.children || [];
        entity.parent.children.push(entity);
      }

      // Fill entities
      valueEntities[value] = entity;
      keyEntities[entity.key] = entity;
      posEntities[pos] = entity;

      // Warning user not to use deprecated label prop.
      if (!title && label && !warnDeprecatedLabel) {
        (0, _warning2['default'])(false, '\'label\' in treeData is deprecated. Please use \'title\' instead.');
        warnDeprecatedLabel = true;
      }

      var node = _react2['default'].createElement(_SelectNode2['default'], (0, _extends3['default'])({ key: entity.key }, nodeProps, { title: title || label, label: label, value: value }), traverse(children, pos));

      entity.node = node;

      return node;
    });
  }

  var treeNodes = traverse(list, '0');

  return {
    treeNodes: treeNodes,

    valueEntities: valueEntities,
    keyEntities: keyEntities,
    posEntities: posEntities
  };
}

/**
 * Detect if position has relation.
 * e.g. 1-2 related with 1-2-3
 * e.g. 1-3-2 related with 1
 * e.g. 1-2 not related with 1-21
 */
function isPosRelated(pos1, pos2) {
  var fields1 = pos1.split('-');
  var fields2 = pos2.split('-');

  var minLen = Math.min(fields1.length, fields2.length);
  for (var i = 0; i < minLen; i += 1) {
    if (fields1[i] !== fields2[i]) {
      return false;
    }
  }
  return true;
}

/**
 * This function is only used on treeNode check (none treeCheckStrictly but has searchInput).
 * We convert entity to { node, pos, children } format.
 * This is legacy bug but we still need to do with it.
 * @param entity
 */
function cleanEntity(_ref3) {
  var node = _ref3.node,
      pos = _ref3.pos,
      children = _ref3.children;

  var instance = {
    node: node,
    pos: pos
  };

  if (children) {
    instance.children = children.map(cleanEntity);
  }

  return instance;
}

/**
 * Get a filtered TreeNode list by provided treeNodes.
 * [Legacy] Since `Tree` use `key` as map but `key` will changed by React,
 * we have to convert `treeNodes > data > treeNodes` to keep the key.
 * Such performance hungry!
 */
function getFilterTree(treeNodes, searchValue, filterFunc) {
  if (!searchValue) {
    return null;
  }

  function mapFilteredNodeToData(node) {
    if (!node) return null;

    var match = false;
    if (filterFunc(searchValue, node)) {
      match = true;
    }

    var children = (_react2['default'].Children.map(node.props.children, mapFilteredNodeToData) || []).filter(function (n) {
      return n;
    });

    if (children.length || match) {
      return (0, _extends3['default'])({}, node.props, {
        key: node.key,
        children: children
      });
    }

    return null;
  }

  return convertDataToEntities(treeNodes.map(mapFilteredNodeToData).filter(function (node) {
    return node;
  })).treeNodes;
}

// =================== Value ===================
/**
 * Convert value to array format to make logic simplify.
 */
function formatInternalValue(value, props) {
  var valueList = toArray(value);

  // Parse label in value
  if (isLabelInValue(props)) {
    return valueList.map(function (val) {
      if ((typeof val === 'undefined' ? 'undefined' : (0, _typeof3['default'])(val)) !== 'object' || !val) {
        return {
          value: '',
          label: ''
        };
      }

      return val;
    });
  }

  return valueList.map(function (val) {
    return {
      value: val
    };
  });
}

function getLabel(wrappedValue, entity, treeNodeLabelProp) {
  if (wrappedValue.label) {
    return wrappedValue.label;
  }

  if (entity && entity.node.props) {
    return entity.node.props[treeNodeLabelProp];
  }

  // Since value without entity will be in missValueList.
  // This code will never reached, but we still need this in case.
  return wrappedValue.value;
}

/**
 * Convert internal state `valueList` to user needed value list.
 * This will return an array list. You need check if is not multiple when return.
 *
 * `allCheckedNodes` is used for `treeCheckStrictly`
 */
function formatSelectorValue(valueList, props, valueEntities) {
  var treeNodeLabelProp = props.treeNodeLabelProp,
      treeCheckable = props.treeCheckable,
      treeCheckStrictly = props.treeCheckStrictly,
      showCheckedStrategy = props.showCheckedStrategy;

  // Will hide some value if `showCheckedStrategy` is set

  if (treeCheckable && !treeCheckStrictly) {
    var values = {};
    valueList.forEach(function (wrappedValue) {
      values[wrappedValue.value] = wrappedValue;
    });
    var hierarchyList = flatToHierarchy(valueList.map(function (_ref4) {
      var value = _ref4.value;
      return valueEntities[value];
    }));

    if (showCheckedStrategy === _strategies.SHOW_PARENT) {
      // Only get the parent checked value
      return hierarchyList.map(function (_ref5) {
        var value = _ref5.node.props.value;
        return {
          label: getLabel(values[value], valueEntities[value], treeNodeLabelProp),
          value: value
        };
      });
    } else if (showCheckedStrategy === _strategies.SHOW_CHILD) {
      // Only get the children checked value
      var targetValueList = [];

      // Find the leaf children
      var traverse = function traverse(_ref6) {
        var value = _ref6.node.props.value,
            children = _ref6.children;

        if (!children || children.length === 0) {
          targetValueList.push({
            label: getLabel(values[value], valueEntities[value], treeNodeLabelProp),
            value: value
          });
          return;
        }

        children.forEach(function (entity) {
          traverse(entity);
        });
      };

      hierarchyList.forEach(function (entity) {
        traverse(entity);
      });

      return targetValueList;
    }
  }

  return valueList.map(function (wrappedValue) {
    return {
      label: getLabel(wrappedValue, valueEntities[wrappedValue.value], treeNodeLabelProp),
      value: wrappedValue.value
    };
  });
}

/**
 * When user search the tree, will not get correct tree checked status.
 * For checked key, use the `rc-tree` `calcCheckStateConduct` function.
 * For unchecked key, we need the calculate ourselves.
 */
function calcUncheckConduct(keyList, uncheckedKey, keyEntities) {
  var myKeyList = keyList.slice();

  function conductUp(conductKey) {
    myKeyList = myKeyList.filter(function (key) {
      return key !== conductKey;
    });

    // Check if need conduct
    var parentEntity = keyEntities[conductKey].parent;
    if (parentEntity && myKeyList.some(function (key) {
      return key === parentEntity.key;
    })) {
      conductUp(parentEntity.key);
    }
  }

  function conductDown(conductKey) {
    myKeyList = myKeyList.filter(function (key) {
      return key !== conductKey;
    });

    (keyEntities[conductKey].children || []).forEach(function (childEntity) {
      conductDown(childEntity.key);
    });
  }

  conductUp(uncheckedKey);
  conductDown(uncheckedKey);

  return myKeyList;
}